// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright (C) 2016-2019, LomboTech Co.Ltd.
 * Authors:
 *	lomboswer <lomboswer@lombotech.com>
 *
 * This program is free software; you can redistribute  it and/or modify it
 * under  the terms of  the GNU General  Public License as published by the
 * Free Software Foundation;  either version 2 of the  License, or (at your
 * option) any later version.
 */

#include "lombo_dc_priv.h"

/* update flag for atomic pageflip flush */
enum {
	LOMBO_DC_WIN_UPDATE_BIT_WIN = 1,
	LOMBO_DC_WIN_UPDATE_BIT_SE = 1 << 1,
	LOMBO_DC_WIN_UPDATE_BIT_OVE = 1 << 2,

	LOMBO_DC_WIN_UPDATE_FLAG_WIN =
		LOMBO_DC_WIN_UPDATE_BIT_WIN,
	LOMBO_DC_WIN_UPDATE_FLAG_SE_WIN =
		LOMBO_DC_WIN_UPDATE_BIT_WIN |
		LOMBO_DC_WIN_UPDATE_BIT_SE,
	LOMBO_DC_WIN_UPDATE_FLAG_OVE_WIN =
		LOMBO_DC_WIN_UPDATE_BIT_WIN |
		LOMBO_DC_WIN_UPDATE_BIT_OVE,
	LOMBO_DC_WIN_UPDATE_FLAG_OVE_SE_WIN =
		LOMBO_DC_WIN_UPDATE_BIT_WIN |
		LOMBO_DC_WIN_UPDATE_BIT_SE |
		LOMBO_DC_WIN_UPDATE_BIT_OVE,
};

static int lombo_dc_win_rot_vfb(struct lombo_dc *dc,
	struct lombo_dc_fbdev_info *fbdev)
{
	struct lombo_rot_client *rot;
	struct drm_framebuffer *dst_fb;
	int ret;

	if (!fbdev->rot)
		fbdev->rot = lombo_rot_get_client(NULL, dc->drm);
	if (!fbdev->rot) {
		DRM_ERROR("null rot client\n");
		return -EINVAL;
	}
	rot = fbdev->rot;

	if (fbdev->num == 1)
		fbdev->fb_rotated = 0;
	else
		fbdev->fb_rotated = fbdev->fb_cur ? 0 : 1;
	dst_fb = fbdev->fbs[fbdev->fb_rotated];

	ret = rot->exec_rot(rot, fbdev->rotate,
		      fbdev->vfb, dst_fb,
		      NULL, NULL, NULL);
	if (ret)
		DRM_ERROR("rot client exec rot fail, ret=%d\n", ret);

	return ret;
}

static inline void lombo_dc_win_rm_se(struct lombo_dc_win *win)
{
	unsigned long flags;
	int ret = 0;
	struct lombo_se *se = NULL;

	spin_lock_irqsave(&win->selock, flags);
	if (!win->rm_se) {
		spin_unlock_irqrestore(&win->selock, flags);
		return;
	}
	se = win->rm_se;
	win->rm_se = NULL;
	win->se_rming = 1;
	spin_unlock_irqrestore(&win->selock, flags);

	ret = lombo_se_release(se, win, 1);
	win->se_rming = 0;
	if ((ret == 0) || (ret == 1))
		DRM_DEBUG_KMS("release se as %s\n", ret ? "minor" : "major");
	else
		DRM_ERROR("release se failed, ret=%d\n", ret);
}

static void lombo_dc_win_req_se(struct lombo_dc_win *win)
{
	uint32_t cnt = 3000;
	struct lombo_se *se = NULL;

	/* req from rm_se first */
	while (cnt--) {
		spin_lock(&win->selock);
		/* make sure rm_se is not rming */
		if (!win->se_rming) {
			se = win->rm_se;
			win->rm_se = NULL;
			spin_unlock(&win->selock);
			break;
		}
		spin_unlock(&win->selock);
		usleep_range(500, 1000);
	}
	if (se) {
		win->se = se;
		DRM_DEBUG_KMS("win%d req se%d, cnt=%d\n",
			      win->id, lombo_se_id(se), cnt);
		return;
	}

	if (cnt < 0) {
		DRM_ERROR("req se timeout\n");
		return;
	}
	win->se = lombo_se_request(&win->se_fb_cfg);
}

static int lombo_dc_win_atomic_check(struct drm_plane *plane,
	struct drm_plane_state *state)
{
	struct drm_crtc *crtc = state->crtc;
	struct drm_crtc_state *crtc_state;

	DRM_DEBUG_ATOMIC("%s:%d\n", __func__, __LINE__);

	if (!crtc)
		return 0;

	crtc_state = drm_atomic_get_existing_crtc_state(state->state, crtc);
	if (WARN_ON(!crtc_state))
		return -EINVAL;

	return 0;
}

static void lombo_dc_win_atomic_disable(struct drm_plane *plane,
	struct drm_plane_state *old_state)
{
	struct lombo_dc_win *win = to_lombo_dc_win(plane);
	struct lombo_dc *dc = win->dc;
	struct lombo_dc_fbdev_info *fbdev = &dc->actual_fbdev;
	int dc_id = dc->id;
	int win_id = win->id;
	dpu_rectangle_t rect = { 0, 0, 0, 0 };

	DRM_DEBUG_ATOMIC("dc%d.win%d\n", dc_id, win_id);

	if (old_state->fb == fbdev->vfb && fbdev->rotate) {
		DRM_DEBUG_ATOMIC("rm vfb\n");
		if (fbdev->rot) {
			fbdev->rot->client_destroy(fbdev->rot);
			fbdev->rot = NULL;
		}
		fbdev->fb_cur = fbdev->num - 1;
	}

	if (win->ove) {
		lombo_ove_disable_plane(win->ove, win);
		lombo_ove_release(win->ove, win, 0);
		win->commit_ove = win->ove;
		win->ove = NULL;
	}

	if (win->se) {
		lombo_se_disable_fb(win->se, win);
		win->se_commit = 1;
		win->se_disable = 1;
	}

	win->fb_cfg.fb = NULL;

	/* just reset winctl-reg to default values */
	csp_dc_window_set_work_mode(dc_id, win->id, DPU_WINDOW_NORMAL_MODE);
	csp_dc_window_set_data_source(dc_id, win->id, DC_WINDOW_SRC_IDMA);
	csp_dc_window_set_alpha_mode(dc_id, win->id, DPU_PIXEL_ALPHA);
	csp_dc_window_set_alpha_value(dc_id, win->id, 0);

	csp_dc_window_set_dest_rectangle(dc_id, win->id, 1, &rect);
	csp_dc_window_hide(dc_id, win->id);

}

static void lombo_dc_win_atomic_update(struct drm_plane *plane,
	struct drm_plane_state *old_state)
{
	struct lombo_dc_win *win = to_lombo_dc_win(plane);
	struct lombo_dc *dc = win->dc;
	struct lombo_dc_fbdev_info *fbdev = &dc->actual_fbdev;
	struct lombo_fb_cfg *fb_cfg = &win->fb_cfg;
	const int dc_id = dc->id;
	const int win_id = win->id;

	struct drm_plane_state *state = plane->state;
	struct drm_framebuffer *fb = state->fb;
	const struct drm_format_info *fmt_info = fb->format;

	dc_window_src_t win_src = DC_WINDOW_SRC_IDMA;
	dpu_alpha_mode_t alpha_mode;
	dpu_window_work_mode_t win_work_mode = DPU_WINDOW_NORMAL_MODE;
	dpu_fb_t dpu_fb;
	dpu_rectangle_t rect[3] = { { 0 } };
	uint32_t update_flag = LOMBO_DC_WIN_UPDATE_FLAG_WIN;
	uint8_t need_scale = 0;
	int i;
	int ret = 0;

	DRM_DEBUG_ATOMIC("dc%d.win%d, fmt=0x%x\n",
			 dc_id, win_id, fmt_info->format);

	fb_cfg->fb = fb;
	fb_cfg->src_x = state->src_x;
	fb_cfg->src_y = state->src_y;
	fb_cfg->src_w = state->src_w;
	fb_cfg->src_h = state->src_h;
	fb_cfg->crtc_x = state->crtc_x;
	fb_cfg->crtc_y = state->crtc_y;
	fb_cfg->crtc_w = state->crtc_w;
	fb_cfg->crtc_h = state->crtc_h;
	fb_cfg->rotation = state->rotation;
	win->alpha = state->alpha;

	if (fb == fbdev->vfb && fbdev->rotate) {
		/* rotate vfb to actual-fb */
		struct lombo_dc_fbdev_info *fbdev = &dc->actual_fbdev;

		DRM_DEBUG_ATOMIC("rot %d vfb, src[%d,%d,%d,%d]\n",
			fbdev->rotate,
			fb_cfg->src_x >> 16, fb_cfg->src_y >> 16,
			fb_cfg->src_w >> 16, fb_cfg->src_h >> 16);

		ret = lombo_dc_win_rot_vfb(dc, fbdev);
		if (!ret) {
			fb = fbdev->fbs[fbdev->fb_rotated];
			fbdev->fb_cur = fbdev->fb_rotated;
			fb_cfg->fb = fb;
			/* FIXME: fix src/crop for no-full-src */
			if (fbdev->rotate & LOMBO_DRM_TRANSFORM_ROT_90)
				swap(fb_cfg->src_w, fb_cfg->src_h);
		}
	}

	need_scale = ((state->crtc_w << 16) != fb_cfg->src_w) ||
		     ((state->crtc_h << 16) != fb_cfg->src_h);
	if (fmt_info->is_yuv || need_scale) {
		/* need se */
		if (!win->se) {
			win->se_fb_cfg.reqer = (void *)win;
			win->se_fb_cfg.in_sel = LOMBO_SE_IN_SEL_DRAM;
			win->se_fb_cfg.in_fmt = fmt_info->format;
			win->se_fb_cfg.run_mode = LOMBO_SE_RUN_MODE_ONLINE;
			win->se_fb_cfg.out_fmt = DRM_FORMAT_ARGB8888;
			win->se_fb_cfg.scn_interlace = dc->ecrtc->is_interlace;
			win->se_fb_cfg.fb_cfg = &win->fb_cfg;
			if (!dc->sleep) {
				lombo_dc_win_req_se(win);
				if (!win->se) {
					DRM_ERROR("req se for win%d fail\n",
						win_id);
					return;
				}
			}
		}
		if (!dc->sleep) {
			update_flag |= LOMBO_DC_WIN_UPDATE_BIT_SE;
			DRM_DEBUG_KMS("add se to flag 0x%x\n", update_flag);
		}
	}

	if (fb->modifier == DRM_FORMAT_MOD_LOMBO_FB_ARRAY) {
		if (!win->ove) {
			win->ove_fb_cfg.reqer = (void *)win;
			if (update_flag & LOMBO_DC_WIN_UPDATE_BIT_SE)
				win->ove_fb_cfg.out_sel = LOMBO_OVE_OUT_SE0 +
					lombo_se_id(win->se);
			else
				win->ove_fb_cfg.out_sel = LOMBO_OVE_OUT_DC;
			win->ove_fb_cfg.fb_cfg = &win->fb_cfg;
			win->ove = lombo_ove_request(&win->ove_fb_cfg);
			if (!win->ove) {
				DRM_ERROR("req ove for win%d fail\n", win_id);
				return;
			}
		}
		update_flag |= LOMBO_DC_WIN_UPDATE_BIT_OVE;
		DRM_DEBUG_KMS("add ove to flag 0x%x\n", update_flag);
	}

	if (update_flag & LOMBO_DC_WIN_UPDATE_BIT_OVE) {
		ret = lombo_ove_update_plane(win->ove, &win->ove_fb_cfg);
		if (ret)
			return;
		win->commit_ove = win->ove;
		fmt_info = drm_format_info(win->ove_fb_cfg.out_fmt);
		win_src = DC_WINDOW_SRC_OVE;
	} else {
		if (win->ove) {
			lombo_ove_disable_plane(win->ove, win);
			lombo_ove_release(win->ove, win, 0);
			win->commit_ove = win->ove;
			win->ove = NULL;
		}
	}

	if (update_flag & LOMBO_DC_WIN_UPDATE_BIT_SE) {
		int se_id;

		if (update_flag & LOMBO_DC_WIN_UPDATE_BIT_OVE) {
			win->se_fb_cfg.in_sel = LOMBO_SE_IN_SEL_DC;
			win->se_fb_cfg.in_fmt = win->ove_fb_cfg.out_fmt;
		}

		ret = lombo_se_update_fb(win->se, &win->se_fb_cfg);
		if (ret)
			return;
		win->se_commit = 1;
		win->se_disable = 0;
		fmt_info = drm_format_info(win->se_fb_cfg.out_fmt);
		se_id = lombo_se_id(win->se);
		if (se_id < 0) {
			DRM_ERROR("bad se_id %d\n", se_id);
			return;
		}
		win_src = DC_WINDOW_SRC_SCALING0 + se_id;
	} else {
		if (win->se) {
			lombo_se_disable_fb(win->se, win);
			win->se_commit = 1;
			win->se_disable = 1;
		}
	}

	if (plane->type == DRM_PLANE_TYPE_PRIMARY) {
		DRM_DEBUG_KMS("win%d:opaque_num=%d\n",
			      state->zpos, win->opaque_num);
		if (win->opaque_num > 1 || (win->opaque_num == 0)) {
			lombo_dc_blk_update_fbdev_for_win(dc, win, fb);
			csp_dc_window_set_work_mode(dc->id, state->zpos,
						    DPU_WINDOW_NORMAL_MODE);
			csp_dc_window_set_data_source(dc->id, state->zpos,
						      DC_WINDOW_SRC_IDMA);
			csp_dc_window_set_alpha_mode(dc->id, state->zpos,
						     DPU_PIXEL_ALPHA);
			csp_dc_window_set_alpha_value(dc->id, state->zpos,
						      state->alpha >> 8);
			csp_dc_window_hide(dc->id, state->zpos);
			return;
		} else if (win->opaque_num == 1) {
			DRM_DEBUG_KMS("win%d 1 opaque[%u,%u,%u,%u]\n",
				state->zpos,
				win->opaque[0].x, win->opaque[0].y,
				win->opaque[0].w, win->opaque[0].h);
			DRM_DEBUG_KMS("scn[%u,%u,%u,%u]\n",
				state->crtc_x, state->crtc_y,
				state->crtc_w, state->crtc_h);
			DRM_DEBUG_KMS("src[%u,%u,%u,%u]\n",
				state->src_x >> 16, state->src_y >> 16,
				state->src_w >> 16, state->src_h >> 16);
			state->crtc_x = win->opaque[0].x;
			state->crtc_y = win->opaque[0].y;
			state->crtc_w = win->opaque[0].w;
			state->crtc_h = win->opaque[0].h;
			fb_cfg->src_x += (state->crtc_x << 16);
			fb_cfg->src_y += (state->crtc_y << 16);
			fb_cfg->src_w = (state->crtc_w << 16);
			fb_cfg->src_h = (state->crtc_h << 16);
		}
		lombo_dc_blk_update_fbdev_for_win(dc, win, NULL);
	}
	csp_dc_window_set_data_source(dc_id, win_id, win_src);
	if (fb->modifier == DRM_FORMAT_MOD_LOMBO_SC) {
		uint32_t color = lombo_fb_sc_get_color(fb, 0);

		csp_dc_window_set_fillcolor(dc_id, win_id,
					    (const dpu_color_t *)&color);
		win_work_mode = DPU_WINDOW_FILLCOLOR_MODE;
	}
	csp_dc_window_set_work_mode(dc_id, win_id, win_work_mode);

	/**
	 * XRGB are not the defined formats to dpu.
	 * the X of XRGB should be regraded as invalid.
	 * so plane_alpha is set for XRGB.
	 */
	if (!fmt_info->has_alpha)
		alpha_mode = DPU_PLANE_ALPHA; /* no-px-al */
	else if (state->alpha != DRM_BLEND_ALPHA_OPAQUE)
		alpha_mode = DPU_PLANE_PIXEL_ALPHA; /* px-al & gb-al */
	else
		alpha_mode = DPU_PIXEL_ALPHA; /* px-al & no-gb-al */
	csp_dc_window_set_alpha_mode(dc_id, win_id, alpha_mode);
	csp_dc_window_set_alpha_value(dc_id, win_id, state->alpha >> 8);
	csp_dc_window_set_blend_rule(dc_id, win_id, 0, DPU_BLEND_SRC_OVER);

	memset((void *)&dpu_fb, 0, sizeof(dpu_fb));
	dpu_fb.format = lombo_dc_convert_drm_format(fmt_info->format);
	dpu_fb.pixel_order = DPU_PO_NORMAL;
	dpu_fb.planes = fmt_info->num_planes;
	for (i = 0; i < dpu_fb.planes; i++) {
		uint32_t x, y, w, h;

		if (i != 0) {
			x = (fb_cfg->src_x >> 16) / fmt_info->hsub;
			y = (fb_cfg->src_y >> 16) / fmt_info->vsub;
			w = (fb_cfg->src_w >> 16) / fmt_info->hsub;
			h = (fb_cfg->src_h >> 16) / fmt_info->vsub;
			rect[i].x = state->crtc_x / fmt_info->hsub;
			rect[i].y = state->crtc_y / fmt_info->vsub;
			rect[i].w = state->crtc_w / fmt_info->hsub;
			rect[i].h = state->crtc_h / fmt_info->vsub;
		} else {
			x = fb_cfg->src_x >> 16;
			y = fb_cfg->src_y >> 16;
			w = fb_cfg->src_w >> 16;
			h = fb_cfg->src_h >> 16;
			rect[i].x = state->crtc_x;
			rect[i].y = state->crtc_y;
			rect[i].w = state->crtc_w;
			rect[i].h = state->crtc_h;
		}

		if ((win_src == DC_WINDOW_SRC_IDMA) &&
			(win_work_mode != DPU_WINDOW_FILLCOLOR_MODE)) {
			struct drm_gem_cma_object *cma_obj =
				drm_fb_cma_get_gem_obj(fb, i);

			dpu_fb.addr[i] = cma_obj->paddr + fb->offsets[i]
				+ fb->pitches[i] * y + x * fmt_info->cpp[i];
			dpu_fb.addr[i] <<= 3; /* in bit unit */
			dpu_fb.linestride_bit[i] = fb->pitches[i] << 3;
		}
		dpu_fb.width[i] = w;
		dpu_fb.height[i] = h;
		DRM_DEBUG_KMS("dpu_fb[%d]:addr[0x%llx],size[%ux%u],lst=%u\n",
			      i, dpu_fb.addr[i],
			      dpu_fb.width[i], dpu_fb.height[i],
			      dpu_fb.linestride_bit[i]);
	}
	csp_dc_window_set_buffer(dc_id, win_id, &dpu_fb);
	csp_dc_window_set_dest_rectangle(dc_id, win_id, dpu_fb.planes, rect);

	csp_dc_window_show(dc_id, win_id);
}

static const struct drm_plane_helper_funcs dc_win_helper_funcs = {
	.atomic_check	= lombo_dc_win_atomic_check,
	.atomic_disable	= lombo_dc_win_atomic_disable,
	.atomic_update	= lombo_dc_win_atomic_update,
};

static bool lombo_dc_win_format_mod_supported(struct drm_plane *plane,
	uint32_t format, uint64_t modifier)
{
	struct lombo_dc_win *win = to_lombo_dc_win(plane);
	struct lombo_dc *dc = win->dc;

	switch (modifier) {
	case DRM_FORMAT_MOD_LOMBO_SC:
		if (dc->cfg->no_win_sc)
			return false;
		if ((format != DRM_FORMAT_ARGB8888) &&
			(format != DRM_FORMAT_XRGB8888)) {
			DRM_INFO("0x%x for fb_sc\n", format);
			return false;
		}
		break;
	default:
		break;
	}

	return true;
}

static const struct drm_plane_funcs dc_win_funcs = {
	.atomic_destroy_state	= drm_atomic_helper_plane_destroy_state,
	.atomic_duplicate_state	= drm_atomic_helper_plane_duplicate_state,
	.destroy		= drm_plane_cleanup,
	.disable_plane		= drm_atomic_helper_disable_plane,
	.reset			= drm_atomic_helper_plane_reset,
	.update_plane		= drm_atomic_helper_update_plane,
	.format_mod_supported	= lombo_dc_win_format_mod_supported,
};

static const uint32_t dc_win_formats[] = {

	DRM_FORMAT_ARGB8888,
	DRM_FORMAT_ABGR8888,
	DRM_FORMAT_RGBA8888,
	DRM_FORMAT_BGRA8888,

	DRM_FORMAT_XRGB8888,
	DRM_FORMAT_XBGR8888,
	DRM_FORMAT_RGBX8888,
	DRM_FORMAT_BGRX8888,

	DRM_FORMAT_RGB888,
	DRM_FORMAT_BGR888,

	DRM_FORMAT_RGB565,
	DRM_FORMAT_BGR565,

	DRM_FORMAT_ARGB1555,
	DRM_FORMAT_ABGR1555,
	DRM_FORMAT_RGBA5551,
	DRM_FORMAT_BGRA5551,

	DRM_FORMAT_XRGB1555,
	DRM_FORMAT_XBGR1555,
	DRM_FORMAT_RGBX5551,
	DRM_FORMAT_BGRX5551,

	DRM_FORMAT_ARGB4444,
	DRM_FORMAT_ABGR4444,
	DRM_FORMAT_RGBA4444,
	DRM_FORMAT_BGRA4444,

	DRM_FORMAT_XRGB4444,
	DRM_FORMAT_XBGR4444,
	DRM_FORMAT_RGBX4444,
	DRM_FORMAT_BGRX4444,

	/* support yuv due to se */

	DRM_FORMAT_NV12,
	DRM_FORMAT_NV21,
	DRM_FORMAT_NV16,
	DRM_FORMAT_NV61,

	DRM_FORMAT_YUV420,
	DRM_FORMAT_YVU420,
	DRM_FORMAT_YUV422,
	DRM_FORMAT_YVU422,

	DRM_FORMAT_YUYV,
	DRM_FORMAT_YVYU,
	DRM_FORMAT_UYVY,
	DRM_FORMAT_VYUY,
};

struct lombo_dc_win *lombo_dc_win_init(struct lombo_dc *dc,
	int win_id, enum drm_plane_type type)
{
	struct lombo_dc_win *win;
	unsigned int supported_rotations = DRM_MODE_ROTATE_0;
	int ret;

	win = devm_kzalloc(dc->dev, sizeof(*win), GFP_KERNEL);
	if (!win)
		return ERR_PTR(-ENOMEM);

	/* possible crtcs are set later */
	ret = drm_universal_plane_init(dc->drm, &win->base, 0,
				       &dc_win_funcs,
				       dc_win_formats,
				       ARRAY_SIZE(dc_win_formats),
				       NULL, type, NULL);
	if (ret) {
		DRM_DEV_ERROR(dc->dev, "plane init fail\n");
		return ERR_PTR(ret);
	}
	drm_plane_helper_add(&win->base, &dc_win_helper_funcs);

	ret = drm_plane_create_zpos_immutable_property(&win->base, win_id);
	if (ret) {
		DRM_DEV_ERROR(dc->dev, "add zpos%d property fail\n", win_id);
		return ERR_PTR(ret);
	}

	/* alpha value of global */
	ret = drm_plane_create_alpha_property(&win->base);
	if (ret) {
		DRM_DEV_ERROR(dc->dev, "Couldn't add alpha property\n");
		return ERR_PTR(ret);
	}

	if (dc->cfg->has_se_hmir)
		supported_rotations |= DRM_MODE_REFLECT_X;
	ret = drm_plane_create_rotation_property(&win->base,
						 DRM_MODE_ROTATE_0,
						 supported_rotations);
	if (ret) {
		DRM_DEV_ERROR(dc->dev, "Couldn't add rotation property\n");
		return ERR_PTR(ret);
	}

	win->dc = dc;
	win->id = win_id;

	spin_lock_init(&win->selock);

	win->opaque_num = -1;

	return win;
}

void lombo_dc_win_commit(struct lombo_dc_win *win)
{
	if (win->commit_ove)
		lombo_ove_commit(win->commit_ove, win);

	if (win->se_commit) {
		lombo_se_commit(win->se, win);
		win->se_commit = 0;
	}
	if (win->se_disable) {
		spin_lock(&win->selock);
		win->rm_se = win->se;
		spin_unlock(&win->selock);
		win->se = NULL;
		win->se_disable = 0;
	}
}

int lombo_dc_win_page_flip_worker(struct lombo_dc_win *win)
{
	if (win->commit_ove) {
		struct lombo_ove *ove = win->commit_ove;

		win->commit_ove = NULL;
		lombo_ove_page_flip_worker(ove, win);
	}

	if (win->rm_se)
		lombo_dc_win_rm_se(win);

	return 0;
}
